<script>
import {
  GlCollapse,
  GlButton,
  GlAlert,
  GlCollapsibleListbox,
  GlSprintf,
  GlFormInput,
  GlLink,
} from '@gitlab/ui';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from 'ee/security_dashboard/utils/event_hub';
import { __, s__, n__ } from '~/locale';
import toast from '~/vue_shared/plugins/global_toast';
import { VULNERABILITY_STATE_OBJECTS, DISMISSAL_REASONS } from 'ee/vulnerabilities/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';

export default {
  components: {
    GlCollapse,
    GlButton,
    GlAlert,
    GlSprintf,
    GlCollapsibleListbox,
    GlFormInput,
    GlLink,
  },
  mixins: [glFeatureFlagMixin()],
  inject: ['vulnerabilitiesQuery', 'vulnerabilitiesCountsQuery'],
  props: {
    selectedVulnerabilities: {
      type: Array,
      required: true,
    },
    visible: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  data() {
    return {
      isSubmitting: false,
      rejectedVulnerabilities: [],
      selectedStatus: null,
      selectedDismissalReason: null,
      comment: null,
    };
  },
  computed: {
    selectedStatusObject() {
      return VULNERABILITY_STATE_OBJECTS[this.selectedStatus];
    },
    statusToggleText() {
      return this.selectedStatusObject?.dropdownText || this.$options.i18n.statusTogglePlaceholder;
    },
    dismissalReasonToggleText() {
      return (
        DISMISSAL_REASONS[this.selectedDismissalReason] ||
        this.$options.i18n.dismissalReasonTogglePlaceholder
      );
    },
    commentPlaceholder() {
      return this.isDismissedStatus
        ? this.$options.i18n.requiredCommentPlaceholder
        : this.$options.i18n.commentPlaceholder;
    },
    statusItems() {
      return Object.values(VULNERABILITY_STATE_OBJECTS).map((vulnerability) => ({
        ...vulnerability,
        value: vulnerability.state,
      }));
    },
    dismissalReasonItems() {
      return Object.entries(DISMISSAL_REASONS).map(([reason, text]) => ({
        value: reason,
        text,
      }));
    },
    isDismissedStatus() {
      return this.selectedStatus === 'dismissed';
    },
    needsDismissalReasonSelection() {
      return this.glFeatures.dismissalReason && this.isDismissedStatus;
    },
    canChange() {
      if (this.needsDismissalReasonSelection) {
        return Boolean(this.selectedDismissalReason) && Boolean(this.comment);
      }

      return Boolean(this.selectedStatus);
    },
  },
  methods: {
    resetSelected() {
      this.$emit('cancel-selection');
      this.resetState();
    },
    resetState() {
      this.selectedStatus = null;
      this.selectedDismissalReason = null;
      this.comment = null;
      this.rejectedVulnerabilities = [];
    },
    updateStatus() {
      this.selectedDismissalReason = null;
      this.comment = null;
    },
    handleSubmit() {
      this.isSubmitting = true;
      this.rejectedVulnerabilities = [];
      let fulfilledCount = 0;

      const promises = this.selectedVulnerabilities.map((vulnerability) => {
        let variables;
        if (this.glFeatures.dismissalReason) {
          variables = { id: vulnerability.id, comment: this.comment };

          if (this.selectedDismissalReason) {
            variables.dismissalReason = this.selectedDismissalReason.toUpperCase();
          }
        } else {
          variables = { id: vulnerability.id, ...this.selectedStatusObject.payload };
        }

        return this.$apollo
          .mutate({
            mutation: this.selectedStatusObject.mutation,
            variables,
            refetchQueries: [this.vulnerabilitiesQuery, this.vulnerabilitiesCountsQuery],
            awaitRefetchQueries: true,
          })
          .then(({ data }) => {
            const [queryName] = Object.keys(data);

            if (data[queryName].errors?.length > 0) {
              throw data[queryName].errors;
            }

            fulfilledCount += 1;
            this.$emit('vulnerability-updated', vulnerability.id);
          })
          .catch(() => {
            this.rejectedVulnerabilities.push(vulnerability);
          });
      });

      return Promise.all(promises).then(async () => {
        this.isSubmitting = false;

        if (fulfilledCount > 0) {
          toast(this.$options.i18n.statusChanged(this.selectedStatusObject.action, fulfilledCount));
          eventHub.$emit('vulnerabilities-updated', this);
          this.resetState();

          // ensure that previously made queries with status filters will be freshly fetched, to not show stale data
          await this.$apollo.getClient().clearStore();
        }
      });
    },
  },
  i18n: {
    cancel: __('Cancel'),
    selected: s__('SecurityReports|%{count} Selected'),
    statusTogglePlaceholder: s__('SecurityReports|Set status'),
    dismissalReasonTogglePlaceholder: s__('SecurityReports|Set dismissal reason'),
    commentPlaceholder: s__('SecurityReports|Add a comment'),
    requiredCommentPlaceholder: s__('SecurityReports|Add a comment (required)'),
    changeStatus: s__('SecurityReports|Change status'),
    statusChanged: (status, count) =>
      ({
        confirm: n__(
          '%d vulnerability set to confirmed',
          '%d vulnerabilities set to confirmed',
          count,
        ),
        resolve: n__(
          '%d vulnerability set to resolved',
          '%d vulnerabilities set to resolved',
          count,
        ),
        dismiss: n__(
          '%d vulnerability set to dismissed',
          '%d vulnerabilities set to dismissed',
          count,
        ),
        revert: n__(
          '%d vulnerability set to needs triage',
          '%d vulnerabilities set to needs triage',
          count,
        ),
      }[status]),
    vulnerabilitiesUpdateFailed: s__(
      'SecurityReports|Failed updating vulnerabilities with the following IDs: %{ids}',
    ),
  },
  getIdFromGraphQLId,
};
</script>

<template>
  <gl-collapse :visible="visible">
    <div :class="{ 'with-error': Boolean(rejectedVulnerabilities.length) }">
      <gl-alert v-if="rejectedVulnerabilities.length" variant="danger" :dismissible="false">
        <gl-sprintf :message="$options.i18n.vulnerabilitiesUpdateFailed">
          <template #ids>
            <span v-for="({ id, vulnerabilityPath }, index) in rejectedVulnerabilities" :key="id">
              <template v-if="index > 0">,</template>
              <gl-link :href="vulnerabilityPath">{{ $options.getIdFromGraphQLId(id) }}</gl-link>
            </span>
          </template>
        </gl-sprintf>
      </gl-alert>

      <form
        class="gl-p-5 gl-bg-gray-10 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100 gl-display-flex gl-flex-wrap gl-align-items-center gl-gap-3"
        @submit.prevent="handleSubmit"
      >
        <div>
          <gl-sprintf :message="$options.i18n.selected">
            <template #count>
              <span class="gl-font-weight-bold">{{ selectedVulnerabilities.length }}</span>
            </template>
          </gl-sprintf>
        </div>

        <gl-collapsible-listbox
          v-model="selectedStatus"
          :items="statusItems"
          :toggle-text="statusToggleText"
          :disabled="isSubmitting"
          block
          class="gl-pl-5 gl-ml-3 gl-border-l"
          data-qa-selector="vulnerability_card_status_dropdown"
          data-testid="status-listbox"
          @select="updateStatus"
        >
          <template #list-item="{ item }">
            <span
              class="gl-display-flex gl-flex-direction-column"
              :data-qa-selector="`item_status_${item.action}`"
              :data-testid="item.action"
            >
              <span class="gl-font-weight-bold gl-white-space-nowrap">{{ item.dropdownText }}</span>
              <span class="gl-text-gray-400"> {{ item.dropdownDescription }}</span>
            </span>
          </template>
        </gl-collapsible-listbox>

        <gl-collapsible-listbox
          v-if="needsDismissalReasonSelection"
          v-model="selectedDismissalReason"
          :items="dismissalReasonItems"
          :toggle-text="dismissalReasonToggleText"
          :disabled="isSubmitting"
          data-testid="dismissal-reason-listbox"
        />

        <gl-form-input
          v-if="selectedStatus && glFeatures.dismissalReason"
          v-model="comment"
          :placeholder="commentPlaceholder"
          :disabled="isSubmitting"
          class="gl-flex-grow-1 gl-flex-basis-0 gl-min-w-20"
        />

        <gl-button
          class="gl-ml-auto"
          :disabled="isSubmitting"
          data-testid="cancel-button"
          @click="resetSelected"
        >
          {{ $options.i18n.cancel }}
        </gl-button>
        <gl-button
          data-qa-selector="change_status_button"
          type="submit"
          variant="confirm"
          :loading="isSubmitting"
          :disabled="!canChange"
        >
          {{ $options.i18n.changeStatus }}
        </gl-button>
      </form>
    </div>
  </gl-collapse>
</template>
